/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.persistence.sql;

import java.io.Serializable;
import java.util.Map;

import org.telosys.persistence.Joker;
import org.telosys.persistence.TelosysPersistenceException;

/**
 * A Criteria object is a collection of Criterion objects to be used together
 * to build dynamic SQL Queries
 * 
 * @author Laurent GUERIN
 *  
 */
public class Criteria implements Serializable
{
	private static final long serialVersionUID = 1L;

	private Criterion[]   _criteria  = null; // index : from 1 to N

    private int           _iCount    = 0;

    private String        _sOperator = "and";

    //---------------------------------------------------------------------------
    /**
     * Build a new criteria ( collection of criterion objects ) with the default "and" operator
     * 
     * @param critArray array of criterion objects to be used in conjunction for dynamic request
     * @since 1.1.0
     */
    public Criteria(Criterion[] critArray)
    {
        this(critArray, "and");
    }
    
    //---------------------------------------------------------------------------
    /**
     * Build a new criteria ( collection of criterion objects )
     * 
     * @param critArray
     *            array of criterion objects to be used in conjunction for dynamic request
     * @param sOperator
     *            where operator to use between each criterion ( "and" / "or" )
     */
    public Criteria(Criterion[] critArray, String sOperator)
    {
        // Duplicate the given array in a N+1 array
        if (critArray != null)
        {
            _criteria = new Criterion[critArray.length + 1];
            _criteria[0] = null;
            for ( int i = 0 ; i < critArray.length ; i++ )
            {
                Criterion c = critArray[i] ;
                if ( c != null )
                {
                    _criteria[i + 1] = c;
                }
                else
                {
                    throw new TelosysPersistenceException("Invalid Criterion ( input Criterion [" + i + "] is null )");
                }
            }
            _iCount = critArray.length;
        }
        else
        {
            _criteria = new Criterion[1];
            _criteria[0] = null;
            _iCount = 0;
        }
        _sOperator = sOperator;
    }

    //---------------------------------------------------------------------------
    /**
     * Returns the current operator for criteria combination ( "and" or "or" )
     * @return
     */
    public String getOperator()
    {
        return _sOperator;
    }

    //---------------------------------------------------------------------------
    /**
     * Returns the number of criteria
     * @return
     */
    public int getCount()
    {
        return _iCount;
    }

    //---------------------------------------------------------------------------
    /**
     * Return the SQL criterion expression ( ie "thecolumn = ?" )
     * 
     * @param i
     *            criterion index ( from 1 to N )
     * @return
     */
    public String getSqlExpression(int i)
    {
        if (_criteria != null)
        {
            if (i > 0 && i < _criteria.length)
            {
                return _criteria[i].getSqlExpression();
            }
        }
        return null;
    }

    //---------------------------------------------------------------------------
    /**
     * Return the parameter type expected for the given criterion index
     * 
     * @param i
     *            criterion index ( from 1 to N )
     * @return
     */
    //public int getParamType(int i)
    public CriterionType getParamType(int i)
    {
        if (_criteria != null)
        {
            if (i > 0 && i < _criteria.length)
            {
                return _criteria[i].getParamType();
            }
        }
        return CriterionType.UNKNOWN ;
    }

    //---------------------------------------------------------------------------
    /**
     * Apply the given parameters to the criteria, each Criterion parameter name <br>
     * is used to try to find a parameter value in the given Map.<br>
     * If a parameter value is found, it is used <br>
     * If there's no parameter in the Map, the Criterion is declared as "not to be used"<br>
     * If the given map is null, each Criterion is declared as "not to be used"<br>
     * The Map is supposed to provide for each key a single String ( "key" --> "value" ), <br>
     * or an array of Strings (in this case, the first item is used : "key" --> values[0] )
     * 
     * @param params - the parameters to apply
     * @return the number of parameters applied on the criteria 
     */
    public int applyParameters( Map<String,?> params )
    {
    	return applyParameters( params, null );
    }
    
    public int applyParameters( Map<String,?> params, Joker joker )
    {
        if ( null == params ) // no parameters 
        {
            this.reset(); // "do not use" for each criterion
            return 0 ;
        }
        
        int count = 0 ;
        if ( _criteria != null )
        {
            for ( int i=1 ; i < _criteria.length ; i++ ) // for each criterion ( NB : from 1 to N )
            {
        		Criterion criterion = _criteria[i] ;
        		
        		//--- 1) By default : do not use this criterion 
    			criterion.doNotUse();
        		
        		//--- 2) Now, try to find a correct parameter to apply 
        		String sParamName = criterion.getParamName(); // the expected parameter name
        		if ( sParamName != null )
        		{
        		    //--- is there a parameter with this name ?
            		Object oParamValue = params.get(sParamName);
//            		if ( oParamValue != null )
//            		{
//                		if ( oParamValue instanceof String ) // classical parameters Map ( eg : from ScreenRequest )
//                		{
//                    		String sParamValue = (String) oParamValue ; 
//                			criterion.useWithValue(sParamValue);
//                			count++;
//                		}
//                		else if ( oParamValue instanceof String[] ) // parameters with multiple values ( eg : from HttpServletRequest )
//                		{
//                    		String[] sParamValues = (String[]) oParamValue ; 
//                    		if ( sParamValues.length > 0 )
//                    		{
//                    			criterion.useWithValue(sParamValues[0]);
//                    			count++;
//                    		}
//                		}
//            		}
            		
            		String sParamValue = getValueToSet(oParamValue) ;
            		if ( sParamValue != null )
            		{
                        //--- If a Joker replacement is defined => use it
                        if ( joker != null )
                        {
                        	sParamValue = joker.replace(sParamValue);
                        }
                        
            			//--- Apply this parameter to the associated criterion
            			criterion.useWithValue(sParamValue);
            			count++;
            		}
        		}
            }
        }
        return count ;
    }
    //---------------------------------------------------------------------------
    private String getValueToSet(Object oParamValue)
    {
    	String sValue = null ;
		if ( null == oParamValue )
		{
			return null ;
		}

		if ( oParamValue instanceof String ) // classical parameters Map ( eg : from ScreenRequest )
		{
			sValue = (String) oParamValue ; 
		}
		else if ( oParamValue instanceof String[] ) // parameters with multiple values ( eg : from HttpServletRequest )
		{
    		String[] sParamValues = (String[]) oParamValue ; 
    		if ( sParamValues.length > 0 )
    		{
    			sValue = sParamValues[0] ; 
    		}
		}
		else if ( oParamValue instanceof Number ) // ????
		{
			Number number = (Number) oParamValue;
			sValue = number.toString() ;
		}
		else 
		{
			// else : unusable type 
            throw new TelosysPersistenceException("Cannot apply this type of parameter (" + oParamValue.getClass().getName() +")");			
		}
		
		return sValue ;
    }
    
    //---------------------------------------------------------------------------
    /**
     * Fix the given criterion as "useful" and set its parameter value
     * 
     * @param i
     *            criterion index ( from 1 to N )
     * @param sParamVal
     *            criterion parameter value
     */
    public void setCriterion(int i, String sParamVal)
    {
        if (_criteria != null)
        {
            if (i > 0 && i < _criteria.length)
            {
                _criteria[i].useWithValue(sParamVal);
            }
        }
    }

    //---------------------------------------------------------------------------
    /**
     * Fix the given criterion as "not used"
     * 
     * @param i
     *            criterion index ( from 1 to N )
     */
    public void doNotUseCriterion(int i)
    {
        if (_criteria != null)
        {
            if (i > 0 && i < _criteria.length)
            {
                _criteria[i].doNotUse();
            }
        }
    }

    //---------------------------------------------------------------------------
    /**
     * Reset each criterion as "not used" 
     */
    public void reset()
    {
        if (_criteria != null)
        {
            for ( int i = 1 ; i < _criteria.length ; i++)
            {
                Criterion criterion = _criteria[i];
                if (criterion != null)
                {
                    criterion.doNotUse();
                }
            }
        }
    }

    //---------------------------------------------------------------------------
    /**
     * Build the SQL where clause dynamicaly with the useful criterion objects
     * 
     * @return the SQL where clause built
     */
    public String getDynamicWhere()
    {
        int iAddCount = 0;
        StringBuffer sb = new StringBuffer(100);

        for ( int i = 1 ; i < _criteria.length ; i++ )
        {
            Criterion crit = _criteria[i];
            if (crit != null)
            {
                if (crit.isUsed())
                {
                    if (iAddCount > 0)
                    {
                        sb.append(" " + _sOperator + " ");
                    }
                    sb.append(crit.getSqlExpression());
                    iAddCount++;
                }
            }
        }

        String sRet = "";
        if (iAddCount > 0)
        {
            sRet = sb.toString();
        }
        return sRet;
    }

    //---------------------------------------------------------------------------
    /**
     * Return the number of useful parameters
     * 
     * @return
     */
    public int getUsefulParametersCount()
    {
        int iSize = 0;
        for ( int i = 1 ; i < _criteria.length ; i++ )
        {
            Criterion crit = _criteria[i];
            if (crit != null)
            {
                if (crit.isUsed())
                {
                    iSize++;
                }
            }
        }
        return iSize;
    }

    //---------------------------------------------------------------------------
    /**
     * Build a int array containing the useful parameters types
     * 
     * @return
     */
    //public int[] getDynamicTypes()
    public CriterionType[] getDynamicTypes()
    {
        //--- Determine the array size
        int iSize = getUsefulParametersCount();
        //--- Build the String array
        //int[] types = new int[iSize];
        CriterionType[] types = new CriterionType[iSize];
        int x = 0;
        for ( int i = 1 ; i < _criteria.length ; i++ )
        {
            Criterion criterion = _criteria[i];
            if (criterion != null)
            {
                if (criterion.isUsed())
                {
                    if (x < types.length)
                    {
                        types[x++] = criterion.getParamType();
                    }
                }
            }
        }
        return types;
    }

    //---------------------------------------------------------------------------
    /**
     * Build a String array containing the useful parameters values
     * 
     * @return
     */
    public String[] getDynamicParameters()
    {
        //--- Determine the array size
        int iSize = getUsefulParametersCount();
        //--- Build the String array
        String[] strParams = new String[iSize];
        int x = 0;
        for ( int i = 1 ; i < _criteria.length ; i++ )
        {
            Criterion crit = _criteria[i];
            if (crit != null)
            {
                if (crit.isUsed())
                {
                    if (x < strParams.length)
                    {
                        strParams[x++] = crit.getParamValueAsString();
                    }
                }
            }
        }
        return strParams;
    }

    //---------------------------------------------------------------------------
    /**
     * Build an array containing the useful Criterion objects
     * @return
     */
    public Criterion[] getUsefulCriteria()
    {
        //--- Determine the array size
        int iSize = getUsefulParametersCount();
        //--- Build the String array
        Criterion[] criterias = new Criterion[iSize];
        int x = 0;
        for ( int i = 1 ; i < _criteria.length ; i++ )
        {
            Criterion crit = _criteria[i];
            if (crit != null)
            {
                if (crit.isUsed())
                {
                    if (x < criterias.length)
                    {
                        criterias[x++] = crit;
                    }
                }
            }
        }
        return criterias;
    }
}